﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Threading;
using System.Threading.Tasks;

namespace O1.Kernel.Storage.Transactions
{
    [ContractVerification(true)]
    internal sealed class MultiVersionGCQueue : IDisposable
    {
        private readonly MultiVersionArray<byte[]> storage;

        private readonly ConcurrentQueue<List<TxRecord>> queue;

        private Func<List<TxRecord>, bool> queueFunc;

        private bool cancel;

        private long drainingFlag;

        public MultiVersionGCQueue(MultiVersionArray<byte[]> storage)
        {
            Contract.Requires(storage != null);

            this.storage = storage;
            this.queue = new ConcurrentQueue<List<TxRecord>>();
            this.queueFunc = this.FillQueue;
            Task.Factory.StartNew(this.ProcessQueue, TaskCreationOptions.LongRunning);
        }

        public bool EnqueueForGC(List<TxRecord> targets)
        {
            Contract.Requires(targets != null);

            return this.queueFunc(targets); 
        }

        public void Dispose()
        {
            this.cancel = true;
            GC.SuppressFinalize(this);
        }

        private static bool DrainQueue(List<TxRecord> targets)
        {
            return false;
        }

        private bool FillQueue(List<TxRecord> targets)
        {
            if (Interlocked.Read(ref this.drainingFlag) > 0)
            {
                return false;
            }

            this.queue.Enqueue(targets);
            return true;
        }

        private void ProcessQueue()
        {
            var q = this.queue;
            var s = new SpinWait();
            try
            {
                List<TxRecord> targets;
                while (!this.cancel)
                {
                    while (q.TryDequeue(out targets))
                    {
                        this.storage.Collect(targets, MultiVersion.Watermark);
                    }
                    
                    // The busy loop eats CPU. Allow for an occasional yield.
                    s.SpinOnce();
                }

                // Drain queue. The Enqueue method is now returning false and not enqueuing work.
                Thread.MemoryBarrier();
                Interlocked.Increment(ref this.drainingFlag);
                this.queueFunc = DrainQueue;
                Thread.MemoryBarrier();
                while (q.TryDequeue(out targets))
                {
                    this.storage.Collect(targets, MultiVersion.Watermark);
                }
            }
            finally
            {
                this.drainingFlag = 0;
            }
        }

        [ContractInvariantMethod]
        private void ObjectInvariant()
        {
            Contract.Invariant(this.storage != null);
            Contract.Invariant(this.queue != null);
            Contract.Invariant(this.queueFunc != null);
        }
    }
}